﻿// Copyright (c) 2010 http://xbeeservice.codeplex.com
// This source is subject to the Microsoft Public License.
// See http://www.opensource.org/licenses/ms-pl.html
// All other rights reserved.

using System;
using System.Diagnostics;
using System.IO.Ports;
using System.Linq;
using Microsoft.Dss.ServiceModel.Dssp;

using GrommetXBee = Grommet.Net.XBee;

namespace SoCGadget.Robotics.XBee
{
    /// <summary>
    /// Implementation class that acts as a higher level abstraction on top of the Grommet XBee module class.
    /// </summary>
    public class XBeeServiceImpl
	{
        #region Events

        public event EventHandler<DataSampleReceivedArgs> DataSampleReceived;

        #endregion

        #region Static members

        /// <summary>
        /// Searches the known serial ports and finds an xbee device;
        /// </summary>
        /// <param name="baud"></param>
        /// <param name="apiMode"></param>
        /// <returns></returns>
        public static string FindXBeeDevice( int baud, string apiMode )
        {
            string[] portNames = SerialPort.GetPortNames();
            return portNames.FirstOrDefault(portName => TestXBeeDevice(portName, baud, apiMode));
        }

        /// <summary>
        /// Tests the specified COM port for the presence of an XBee device.
        /// </summary>
        /// <param name="portName"></param>
        /// <param name="baud"></param>
        /// <param name="apiMode"></param>
        /// <returns></returns>
        public static bool TestXBeeDevice( string portName, int baud, string apiMode )
        {
            var apiModeEnum = ( GrommetXBee.ApiMode )Enum.Parse( typeof( GrommetXBee.ApiMode ), apiMode, true );
            return GrommetXBee.XBeeModule.TestComPortForXbee( portName, baud, apiModeEnum );
        }

        #endregion

        public XBeeServiceImpl( IXBeeModule xBeeModule )
        {
            _xbee = xBeeModule;
            _xbee.DataSampleReceived += Xbee_DataSampleReceived;
            _xbee.SerialPortErrorReceived += SerialPort_ErrorReceived;
        }

        #region Properties

        /// <summary>
        /// The the class of device (XBeeService)
        /// </summary>
        public string Class
        {
            get { return ControllerClass; }
        }

        /// <summary>
        /// Gets the COM port name the XBee device was found on.
        /// </summary>
        public string ComPortName
        {
            get { return _xbee.SerialPortName; }
        }

        #endregion

        #region Serial communications

        /// <summary>
		/// Sends a string message to an XBee endpoint. This method is asynchronous.
		/// </summary>
        /// <param name="message"></param>
        public void SendMessageToEndPoint( DeviceMessageUpdate message )
		{
            XBeeAddress64 destAddress = message.Body.DestinationAddress;

            // Send the message to the endpoint and process the response.
            _xbee.SendString( destAddress.Address64Bit, destAddress.Address16Bit, message.Body.Message, status =>
				{
                    // Analyze the results and log
                    if ( status != CommandStatus.Success )
                    {
                        var fault = new SoapFault(
                            string.Format( "SendMessageToEndPoint: Failed to send message to device SH=0x{0:x}, SL=0x{1:x}: {2}",
                                message.Body.DestinationAddress.AddressHigh,
                                message.Body.DestinationAddress.AddressLow,
                                status.ToString()
                            ) );
                        Trace.WriteLine( fault.ToString() );
                        message.ResponsePort.Post( fault );
                    }
                    else
                    {
                        message.ResponsePort.Post( new DefaultUpdateResponseType() );
                    }
				}
			);
        }

        /// <summary>
        /// Sends a digital output update message to an XBee endpoint. This method is asynchronous.
        /// </summary>
        /// <param name="message"></param>
        public void SendDigitalOutputToEndPoint( DeviceOutputUpdate message )
        {
            XBeeAddress64 destAddress = message.Body.DestinationAddress;
            string command;

            switch ( message.Body.OutputNumber )
            {
                case 0:
                    command = "D0";
                    break;
                case 1:
                    command = "D1";
                    break;
                case 2:
                    command = "D2";
                    break;
                case 3:
                    command = "D3";
                    break;
                case 4:
                    command = "D4";
                    break;
                case 5:
                    command = "D5";
                    break;
                case 6:
                    command = "D6";
                    break;
                case 7:
                    command = "D7";
                    break;
                // DIO8 not supported
                // DIO9 not supported
                case 10:
                    command = "P0";
                    break;
                case 11:
                    command = "P1";
                    break;
                case 12:
                    command = "P2";
                    break;
                case 13:
                    command = "P3";
                    break;
                default:
                    throw new ArgumentException("Output number not supported.");
            }

            var ioConfigData = ( byte )( message.Body.OutputState ? 0x5 : 0x4 );

            _xbee.SendRemoteCommand( destAddress.Address64Bit, destAddress.Address16Bit, command, ioConfigData, status =>
                {
                    // Analyze the results and log
                    if ( status != CommandStatus.Success )
                    {
                        var fault = new SoapFault(
                            string.Format( "SendDigitalOutputToEndPoint: Failed to send output update message to device SH=0x{0:x}, SL=0x{1:x}: {2}",
                                message.Body.DestinationAddress.AddressHigh,
                                message.Body.DestinationAddress.AddressLow,
                                status.ToString()
                            ) );
                        Trace.WriteLine( fault.ToString() );
                        message.ResponsePort.Post( fault );
                    }
                    else
                    {
                        message.ResponsePort.Post( new DefaultUpdateResponseType() );
                    }
                }
            );
        }

        /// <summary>
        /// We received a data sample from a remote xbee device
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Xbee_DataSampleReceived(object sender, DataSampleReceivedArgs e)
        {
            Trace.WriteLine( string.Format( "Data sample received from Addr={0:x}, SH=0x{1:x}, SL=0x{2:x}",
                                            e.SourceAddress,
                                            e.SourceSerialNumber >> 32,
                                            e.SourceSerialNumber & 0xFFFFFFFF ) );

            // Re-fire the event
            EventHandler<DataSampleReceivedArgs> handler = DataSampleReceived;
            if ( handler != null )
            {
                handler( this, e );
            }
        }

        /// <summary>
        /// Handles the serial port error event from the xbee serial port.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SerialPort_ErrorReceived( object sender, SerialErrorReceivedEventArgs e )
        {
            Trace.WriteLine( string.Format( "Serial communications error: {0}", e ) );
        }

        #endregion

        #region Connection management

        public bool IsConnected
		{
			get
			{
				return _xbee.SerialPortIsOpen;
			}
		}

		public void Connect()
		{
			_xbee.Open();
		}

		public void Disconnect()
		{
			_xbee.Close();
		}

        #endregion

        #region Class data

        private readonly IXBeeModule _xbee;
        private const string ControllerClass = "XBeeService";

        #endregion
    }
}
